#include "timer.h"
#include "types.h"
#include "lib.h"
#include "hwirq.h"
#include "riscv_asm.h"
#include "task.h"
#include "spinlock.h"

#define TIMERS_MAX	10U

static uint32_t _tick = 0;
static struct timer_t timer_list[TIMERS_MAX];
static struct spinlock lock;

static void timer_process(void);

void timer_load(uint64_t interval)
{
	/* each CPU has a separate source of timer interrupts */
	uint64_t hart = csr_read(mhartid);
	*(uint64_t*)CLINT_MTIMECMP(hart) = *(uint64_t*)CLINT_MTIME + interval;

}

void timer_init()
{
	init_lock(&lock, "timer_lock");

	for (uint8_t i = 0; i < TIMERS_MAX; i++) {
		timer_list[i].timer_name = NULL;
		timer_list[i].cb_func = NULL;
		timer_list[i].arg = NULL;
		timer_list[i].timeout_tick = 0;
	}

	/*
	 * On reset, mtime is cleared to zero, but the mtimecmp registers
	 * are not reset. So we have to init the mtimecmp manually.
	 */
	timer_load(10000000);

	/* enable machine-mode timer interrupts */
	csr_write(mie, csr_read(mie)|MIE_MTIE);
}

void timer_handler()
{
	_tick++;
	// mprintf("tick: %d\n", _tick);
	timer_process();

	timer_load(10000000);
	schedule();

}


struct timer_t *timer_create(void (*handler)(void *arg),
								char *timer_name,
								void *arg,
								uint32_t time_out)
{
	if (handler == NULL || time_out == 0U)
		return NULL;

	struct timer_t *t = timer_list;

	acquire_lock(&lock);
	for (uint8_t i = 0; i < TIMERS_MAX; i++) {
		if (t->cb_func == NULL) {
			break;
		}
		t++;
	}

	if (t->cb_func != NULL) {
		return NULL;
	}

	t->timer_name = timer_name;
	t->cb_func = handler;
	t->arg = arg;
	t->timeout_tick = _tick + time_out;
	release_lock(&lock);

	return t;
}

uint32_t timer_delete(struct timer_t *timer)
{
	if (timer == NULL)
		return 0;

	struct timer_t *t = timer_list;
	acquire_lock(&lock);
	for (uint8_t i = 0; i < TIMERS_MAX; i++) {
		if (t->cb_func == timer->cb_func) {
			t->timer_name = NULL;
			t->cb_func = NULL;
			t->arg = NULL;
			t->timeout_tick = 0;
			break;
		}
		t++;
	}
	release_lock(&lock);

	return 0;
}

static void timer_process(void)
{
	struct timer_t *t = timer_list;
	for (uint8_t i = 0; i < TIMERS_MAX; i++) {
		if (t->cb_func) {
			if (_tick >= t->timeout_tick) {
				t->cb_func(t->arg);

				/* once time, just delete it after timeout */
				t->timer_name = NULL;
				t->cb_func = NULL;
				t->arg = NULL;
				t->timeout_tick = 0;
				break;
			}
		}
		t++;
	}
}
